package me.zhengjie.server.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import me.zhengjie.dao.*;
import me.zhengjie.dao.dto.StoreLocalQueryCriteria;
import me.zhengjie.enums.ConstantStatus;
import me.zhengjie.enums.InboundStatusEnum;
import me.zhengjie.mapper.StoreLocalMapper;
import me.zhengjie.mapper.StoreOutsideMapper;
import me.zhengjie.mapper.StoreTotalMapper;
import me.zhengjie.server.StoreLocalService;
import me.zhengjie.server.StoreOutsideService;
import me.zhengjie.server.WareTypeService;
import me.zhengjie.utils.FileUtil;
import me.zhengjie.utils.PageResult;
import me.zhengjie.utils.PageUtil;
import me.zhengjie.vo.Result;
import me.zhengjie.vo.task.TaskViewQueryCriteria;
import me.zhengjie.vo.task.TaskViewVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class StoreLocalServiceImpl extends ServiceImpl<StoreLocalMapper, StoreLocalDao> implements StoreLocalService {

    @Resource
    private StoreLocalMapper storeLocalMapper;

    @Resource
    private StoreOutsideService storeOutsideService;

    @Resource
    private StoreTotalMapper storeTotalMapper;

    @Resource
    private WareTypeService wareTypeService;

    @Resource
    private StoreOutsideMapper storeOutsideMapper;

    @Override
    public PageResult<TaskViewVo> queryAllTaskStatusChange(TaskViewQueryCriteria criteria, Page<Object> page) {
        return PageUtil.toPage(storeTotalMapper.queryAllTaskStatusChange(criteria, page));
    }

    @Override
    public PageResult<StoreLocalDao> queryAll(StoreLocalQueryCriteria criteria, Page<Object> page) {
        return PageUtil.toPage(storeLocalMapper.findAll(criteria, page));
    }

    @Override
    public List<StoreLocalDao> queryAll(StoreLocalQueryCriteria criteria) {
        List<WareTypeDao> typeDaoList = wareTypeService.list();
        Map<String, String> typeMap = new HashMap<>();
        for (WareTypeDao wareTypeDao : typeDaoList) {
            typeMap.put(wareTypeDao.getId(), wareTypeDao.getType());
        }
        List<StoreLocalDao> storeLocalList = storeLocalMapper.findAll(criteria);
        for (StoreLocalDao storeLocalDao : storeLocalList) {
            String wareType = storeLocalDao.getWareType();
            if (!typeMap.containsKey(wareType)) {
                continue;
            }
            storeLocalDao.setWareType(typeMap.get(wareType));
        }
        return storeLocalList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(StoreLocalDao resources) {
        String goodType = resources.getGoodType();
        resources.setCreateDate(new Timestamp(new Date().getTime()));
        if (goodType.equals(ConstantStatus.ORDINARY_SHELVES) ||
                goodType.equals(ConstantStatus.OPERATION_AREA)) {
            storeLocalMapper.insert(resources);
        } else {
            StoreOutsideDao storeOutsideDao = new StoreOutsideDao();
            BeanUtil.copyProperties(resources, storeOutsideDao, CopyOptions.create().setIgnoreNullValue(true));
            storeOutsideService.create(storeOutsideDao);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(StoreLocalDao resources) {
        StoreLocalDao storeLocal = getById(resources.getId());
        if (storeLocal == null) {
            StoreOutsideDao storeOutside = new StoreOutsideDao();
            BeanUtil.copyProperties(resources, storeOutside, CopyOptions.create().setIgnoreNullValue(true));
            StoreOutsideDao storeOutsideDao = storeOutsideService.getById(resources.getId());
            storeOutsideDao.copy(storeOutside);
            storeOutsideDao.setUpdateDate(new Timestamp(new Date().getTime()));
            storeOutsideService.updateById(storeOutsideDao);
        } else {
            storeLocal.setUpdateDate(new Timestamp(new Date().getTime()));
            storeLocal.copy(resources);
            storeLocalMapper.updateById(storeLocal);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAll(List<String> ids) {
        storeLocalMapper.deleteBatchIds(ids);
        storeOutsideMapper.deleteBatchIds(ids);
    }


    public boolean updateDataList(List<String> ids, StoreLocalDao storeLocalDao) {
        LambdaUpdateWrapper<StoreLocalDao> localWrapper = new LambdaUpdateWrapper<>();
        localWrapper.in(StoreLocalDao::getId, ids);
        this.setUpdateWrapper(storeLocalDao, localWrapper);
        return this.update(localWrapper);
    }


    public boolean updateData(StoreLocalDao storeLocalDao) {
        LambdaUpdateWrapper<StoreLocalDao> wrapper = new LambdaUpdateWrapper<>();
        if (storeLocalDao.getId() != null) {
            wrapper.eq(StoreLocalDao::getId, storeLocalDao.getId());
            this.setUpdateWrapper(storeLocalDao, wrapper);
        }
        if (storeLocalDao.getContainerCode() != null) {
            wrapper.eq(StoreLocalDao::getContainerCode, storeLocalDao.getContainerCode());
            this.setUpdateWrapper(storeLocalDao, wrapper);
        }
        return baseMapper.update(null, wrapper) > 0;
    }

    public void setUpdateWrapper(StoreLocalDao storeLocalDao, LambdaUpdateWrapper<StoreLocalDao> wrapper) {
        wrapper.set(StoreLocalDao::getUpdateDate, new Date());
        if (storeLocalDao.getStatus() != null) {
            wrapper.set(StoreLocalDao::getStatus, storeLocalDao.getStatus());
            if (storeLocalDao.getStatus().equals(InboundStatusEnum.STORAGE_FREE.status)) {
                wrapper.set(StoreLocalDao::getContainerCode, null);
            }
            if (storeLocalDao.getStatus().equals(InboundStatusEnum.STORAGE_LOAD.status)) {
                wrapper.set(StoreLocalDao::getContainerCode, storeLocalDao.getContainerCode());
            }
        }
        if (storeLocalDao.getIsLock() != null) {
            wrapper.set(StoreLocalDao::getIsLock, storeLocalDao.getIsLock());
        }
        if (storeLocalDao.getWareType() != null) {
            wrapper.set(StoreLocalDao::getWareType, storeLocalDao.getWareType());
        }
    }

    public List<StoreLocalDao> findDataList(StoreLocalDao storeLocalDao) {
        LambdaQueryWrapper<StoreLocalDao> wrapper = new LambdaQueryWrapper<>();
        this.setFindWrapper(storeLocalDao, wrapper);
        return this.list(wrapper);
    }

    public StoreLocalDao findData(StoreLocalDao storeLocalDao) {
        LambdaQueryWrapper<StoreLocalDao> wrapper = new LambdaQueryWrapper<>();
        this.setFindWrapper(storeLocalDao, wrapper);
        List<StoreLocalDao> localDaoList = this.list(wrapper);
        if (localDaoList == null || localDaoList.isEmpty()) {
            return null;
        }
        return localDaoList.get(0);
//        return this.getOne(wrapper);
    }


    public void setFindWrapper(StoreLocalDao storeLocalDao, LambdaQueryWrapper<StoreLocalDao> wrapper) {

        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getId, storeLocalDao.getId());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getFloor, storeLocalDao.getFloor());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getWareType, storeLocalDao.getWareType());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getStoreType, storeLocalDao.getStoreType());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getGoodType, storeLocalDao.getGoodType());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getContainerCode, storeLocalDao.getContainerCode());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getIsLock, storeLocalDao.getIsLock());
        }
        if (storeLocalDao.getFloor() != null) {
            wrapper.like(StoreLocalDao::getStatus, storeLocalDao.getStatus());
        }
        this.list(wrapper);
    }

    @Override
    public void download(List<StoreLocalDao> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (StoreLocalDao storeLocal : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("储位名称", storeLocal.getStoreName());
            map.put("储位类型", storeLocal.getStoreType());
            map.put("位置", storeLocal.getFloor());
            map.put("区域号", storeLocal.getRegion());
            map.put("层数号", storeLocal.getFrame());
            map.put("储位号", storeLocal.getSerial());
            map.put("库区类型", storeLocal.getWareType());
            map.put("货位类型", storeLocal.getGoodType());
            map.put("载具码", storeLocal.getContainerCode());
            map.put("储位状态", storeLocal.getStatus());
            map.put("是否锁定", storeLocal.getIsLock());
            map.put("创建时间", storeLocal.getCreateDate());
            map.put("更新时间", storeLocal.getUpdateDate());
            map.put("储位重量", storeLocal.getWeight());
            map.put("储位长", storeLocal.getSLong());
            map.put("储位宽", storeLocal.getSWide());
            map.put("储位高", storeLocal.getSTall());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }


    /**
     * 根据物料信息，分配储位
     *
     * @param materialDao 物料信息
     * @param floor       楼层
     * @param goodType    货位类型
     * @return 储位编码
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String findStoreByType(MaterialDao materialDao, String floor, String goodType) {
        // 空箱回库分配
        if (materialDao == null) {
            return emptyContainerStoreId(true, floor, null, null);
        }
        // 根据中间表信息,查询库存数据是否存在空闲储位
        // 物料类型
        String materialCategory = materialDao.getMaterialCategory();
        // 供应商)
        String manufacturers = materialDao.getManufacturers();
        // 物料编码
        String materialCode = materialDao.getMaterialCode();
        // 物料批次号
        String batchCode = materialDao.getBatchCode();
        // 入厂时间
        LocalDateTime entryTime = materialDao.getEntryTime();
        // 是否辅料
        Integer isAuxiliaryMaterials = materialCategory.equals("辅料") ? 1 : 0;

        // 是否辅料, 入场时间,  物料分类， 厂商， 物料编码， 是否靠近出入口
        String storeId = getStoreHandle(isAuxiliaryMaterials, goodType, floor, null,
                entryTime, materialCode, manufacturers, materialCategory);
        // 是否辅料, 物料分类, 厂商, 物料编码
        if (storeId == null) {
            storeId = getStoreHandle(isAuxiliaryMaterials, goodType, floor, null,
                    null, materialCode, manufacturers, materialCategory);
        }
        // 是否辅料, 物料分类, 厂商
        if (storeId == null) {
            storeId = getStoreHandle(isAuxiliaryMaterials, goodType, floor, null,
                    null, null, manufacturers, materialCategory);
        }
        // storeTotalMapper.findIdentityList(0, null, "M2", null, null, "A004", "字节", "原料")
        // 是否辅料, 物料分类
        if (storeId == null) {
            storeId = getStoreHandle(isAuxiliaryMaterials, goodType, floor, null,
                    null, null, null, materialCategory);
        }
//        // 是否辅料, 找一个层数都位空储位
//        if (storeId == null) {
//            storeId = getStoreHandle(isAuxiliaryMaterials, goodType, floor, 1,
//                    null, null, null, null);
//        }
//        // 随便找一个储位
//        if (storeId == null) {
//            storeId = getStoreHandle(isAuxiliaryMaterials, goodType, floor, 2,
//                    null, null, null, null);
//        }
//        // 如果理货区满了，找不到位置.放置其他区域
//        if (storeId == null && goodType != null && goodType.equals("理货区")) {
//            storeId = getStoreHandle(isAuxiliaryMaterials, null, floor, null,
//                    null, null, null, null);
//        }
        // 查找可用储位
        if (storeId == null) {
            storeId = emptyContainerStoreId(false, floor, isAuxiliaryMaterials, goodType);
        }
        return storeId;
    }

    /**
     * 根据物料信息，分配储位
     *
     * @param isEmpty              是否空箱入库
     * @param floor                楼层
     * @param isAuxiliaryMaterials 是否辅料
     * @param goodType             货位类型
     * @return 储位编码
     */
    public String emptyContainerStoreId(Boolean isEmpty, String floor, Integer isAuxiliaryMaterials, String goodType) {

        // 空载具列
        List<String> storeIdList = new ArrayList<>();
        if (isEmpty) {
            storeIdList = storeLocalMapper.findEmptyStore(floor, isAuxiliaryMaterials, goodType);
            if (!storeIdList.isEmpty()) {
                return storeIdList.get(0);
            }
        }
        // // 空储位列
        storeIdList = storeLocalMapper.findEmptySingleStore(floor, isAuxiliaryMaterials, goodType);
        if (!storeIdList.isEmpty()) {
            return storeIdList.get(0);
        }
        // 随便找一个
        storeIdList = storeLocalMapper.findArbitrarySingleStore(floor, isAuxiliaryMaterials, goodType);
        if (!storeIdList.isEmpty()) {
            return storeIdList.get(0);
        }
        // 判断是否理货区
        if (goodType != null && goodType.equals(ConstantStatus.OPERATION_AREA)) {
            storeIdList = storeLocalMapper.findArbitrarySingleStore(floor, isAuxiliaryMaterials, null);
        }
        if (!storeIdList.isEmpty()) {
            return storeIdList.get(0);
        }
        return null;
    }

    /**
     * 分配储位
     *
     * @param isAuxiliaryMaterials 是否辅料
     * @param isCodeNull           是否整列为空
     * @param entryTime            入场时间
     * @param floor                楼层
     * @param materialCode         物料编码
     * @param manufacturers        厂商
     * @param materialCategory     物料分类
     * @return 储位编码
     */
    public String getStoreHandle(
            Integer isAuxiliaryMaterials,
            String goodType,
            String floor,
            Integer isCodeNull,
            LocalDateTime entryTime,
            String materialCode,
            String manufacturers,

            String materialCategory) {
        List<StoreTotalDao> storeList = storeTotalMapper.findIdentityList(
                isAuxiliaryMaterials, goodType, floor, isCodeNull,
                entryTime, materialCode, manufacturers, materialCategory);
        if (storeList.isEmpty()) {
            return null;
        }
        // storeList.sort(Comparator.comparing(StoreTotalDao::getRegion));
        for (StoreTotalDao storeTotalDao : storeList) {
            String sFloor = storeTotalDao.getFloor();
            String region = storeTotalDao.getRegion();
            String serial = storeTotalDao.getSerial();
            StoreLocalDao store = storeLocalMapper.findStoreByCode(sFloor, region, serial);
            if (store != null) {
                return store.getId();
            }
        }
        return null;
    }


    /**
     * 锁定储位
     *
     * @return true/false
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> lockStore(List<String> ids) {
        if (ids.isEmpty()) {
            return Result.error("储位信息不能为空");
        }
        // 释放库内储位
        StoreLocalDao storeLocalDao = new StoreLocalDao();
        storeLocalDao.setIsLock(ConstantStatus.FREEZE);
        boolean localUpdate = this.updateDataList(ids, storeLocalDao);

        // 释放库外储位
        StoreOutsideDao storeOutsideDao = new StoreOutsideDao();
        storeOutsideDao.setIsLock(ConstantStatus.FREEZE);
        boolean outSideUpdate = storeOutsideService.updateDataList(ids, storeOutsideDao);
        if (localUpdate || outSideUpdate) {
            return Result.success("锁定储位成功");
        }
        return Result.error("锁定储位失败");
    }

    /**
     * 解锁储位
     *
     * @param ids 储位信息列表
     * @return true/false
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> unlockStore(List<String> ids) {
        if (ids.isEmpty()) {
            return Result.error("储位信息不能为空");
        }
        // 释放库内储位
        StoreLocalDao storeLocalDao = new StoreLocalDao();
        storeLocalDao.setIsLock(ConstantStatus.NORMAL);
        boolean localUpdate = this.updateDataList(ids, storeLocalDao);

        // 释放库外储位
        StoreOutsideDao storeOutsideDao = new StoreOutsideDao();
        storeOutsideDao.setIsLock(ConstantStatus.NORMAL);
        boolean outSideUpdate = storeOutsideService.updateDataList(ids, storeOutsideDao);
        if (localUpdate || outSideUpdate) {
            return Result.success();
        }
        return Result.error("更新失败");
    }

    /**
     * 释放储位
     *
     * @param ids 储位信息列表
     * @return true/false
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> releaseStore(List<String> ids) {
        if (ids.isEmpty()) {
            return Result.error("储位信息不能为空");
        }
        // 释放库内储位
        StoreLocalDao storeLocalDao = new StoreLocalDao();
        storeLocalDao.setContainerCode(null);
        storeLocalDao.setStatus(InboundStatusEnum.STORAGE_FREE.status);
        storeLocalDao.setIsLock(ConstantStatus.NORMAL);
        boolean localUpdate = this.updateDataList(ids, storeLocalDao);

        // 释放库外储位
        StoreOutsideDao storeOutsideDao = new StoreOutsideDao();
        storeOutsideDao.setContainerCode(null);
        storeOutsideDao.setStatus(InboundStatusEnum.STORAGE_FREE.status);
        storeOutsideDao.setIsLock(ConstantStatus.NORMAL);
        boolean outSideUpdate = storeOutsideService.updateDataList(ids, storeOutsideDao);
        if (localUpdate || outSideUpdate) {
            return Result.success();
        }
        return Result.error();
    }

    /**
     * 更新库区类型
     *
     * @param params 请求参数
     * @return
     */
    @Override
    public Result<?> updateWareType(Map<String, Object> params) {
        String wareType = (String) params.get("wareType");
        List<String> idList = (List<String>) params.get("storeList");
        StoreLocalDao storeLocalDao = new StoreLocalDao();
        storeLocalDao.setWareType(wareType);
        boolean storeLocalUpdate = this.updateDataList(idList, storeLocalDao);

        StoreOutsideDao storeOutsideDao = new StoreOutsideDao();
        storeOutsideDao.setWareType(wareType);
        boolean outsideUpdate = storeOutsideService.updateDataList(idList, storeOutsideDao);
        if (outsideUpdate || storeLocalUpdate) {
            return Result.success("更新成功");
        }
        return Result.fail("更新失败");
    }


}
